package event

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"strconv"
)

const (
	INVALID_EVENT_TYPE = 0
)

type PluginRelation struct {
	Child  string
	Type   int
	Parent string
}

func NewPluginRelation(rel []string) (PluginRelation, error) {
	i := INVALID_EVENT_TYPE
	var err error

	switch len(rel) {
	case 0:
		return PluginRelation{}, errors.New("invalid empty relation item")
	case 1:
		if rel[0] == "" {
			return PluginRelation{}, fmt.Errorf("invalid relation %v", rel)
		}
		return PluginRelation{Child: rel[0], Type: i}, err
	case 2:
		// res[2]should be int
		i, err = strconv.Atoi(rel[1])
		if err != nil {
			return PluginRelation{}, err
		}
		return PluginRelation{Child: rel[0], Type: i}, err
	case 3:
		i, err = strconv.Atoi(rel[1])
		if err != nil {
			return PluginRelation{}, err
		}
		return PluginRelation{Child: rel[0], Parent: rel[2], Type: i}, err
	default:
		return PluginRelation{}, fmt.Errorf("invalid relation %v, length > 3", rel)
	}
}
func (p PluginRelation) IsValid() bool {
	return p.Child != ""
}
func (p PluginRelation) IsMiddleware() bool {
	return p.IsValid() && p.Type == INVALID_EVENT_TYPE
}
func (p PluginRelation) IsProcessor() bool {
	return p.IsValid() && p.Type != INVALID_EVENT_TYPE
}
func (p PluginRelation) HasParent() bool {
	return p.IsValid() && p.Parent != ""
}

type PluginConfig struct {
	// describe child-parent-type relations
	// parent and type can be nil
	Relations [][]string

	// plugin dir, if not given, use "./plugins"
	PluginDir string

	// common plugins, the last plugin will be the parent for all plugins with no parent offered
	CommonMiddlewares []string
}

func ReadConfigAndCreateManager(filepath string, ctx *PluginContext) error {
	conf, err := ReadConfig(filepath)
	if err != nil {
		return err
	}
	conf.CreateProcessorManager(ctx)
	return err
}
func ReadConfig(filepath string) (PluginConfig, error) {
	// open file and deserialize json
	data, err := os.ReadFile(filepath)
	if err != nil {
		return PluginConfig{}, err
	}

	var conf PluginConfig
	if err := json.Unmarshal(data, &conf); err != nil {
		return PluginConfig{}, err
	}
	return conf, nil
}

func (c PluginConfig) GetPluginDir() string {
	if c.PluginDir == "" {
		return "./plugins"
	}
	return c.PluginDir
}

func (c PluginConfig) GetCommonMiddlewares() []string {
	return c.CommonMiddlewares
}

func (c PluginConfig) CreateProcessorManager(ctx *PluginContext) (*ProcessorManager, error) {
	r := NewPluginReader()
	err := r.Read(c.GetPluginDir())
	if err != nil {
		return nil, err
	}

	var mgr *ProcessorManager

	if pm, ok := ctx.ProcManager.(*ProcessorManager); ok {
		mgr = pm
	} else {
		return nil, fmt.Errorf("error ctx")
	}
	// mgr := NewProcessorManager()
	// ctx.ProcManager = mgr

	// add common middlewares
	parent := ""
	for _, name := range c.GetCommonMiddlewares() {
		var plg EventProcessor
		// use common middlewares first
		plg, err := getDefaultPlugin(name)
		if err != nil || !plg.IsMiddleware() {
			plg, err = r.Create(name, ctx)
		}
		if err != nil {
			return nil, err
		}
		if !plg.IsMiddleware() {
			return nil, errors.New("common middleware must be middleware, but " + name + " is not")
		}
		if plg.Name() != name {
			return nil, fmt.Errorf("common middleware name not match: %s != %s", name, plg.Name())
		}
		if parent != "" {

			mgr.AddProcessorWithParent(plg, parent)

		} else {
			mgr.AddProcessor(plg)
		}
		parent = plg.Name()
	}

	// add unregistered event processor
	mgr.AddProcessorWithParent(UnregisteredEventProcessor, parent)

	// add other relations
	for _, rel := range c.Relations {
		relation, err := NewPluginRelation(rel)
		if err != nil {
			return nil, err
		}

		plg, err := r.Create(relation.Child, ctx)
		if err != nil {
			return nil, err
		}
		if plg.IsMiddleware() != relation.IsMiddleware() {
			return nil, fmt.Errorf("plugin %s is not the type in relation: %v", plg.Name(), relation)
		}
		if plg.Name() != relation.Child {
			return nil, fmt.Errorf("plugin name not match config: %s != %s", plg.Name(), relation.Child)
		}
		if relation.HasParent() {
			mgr.AddProcessorWithParent(plg, relation.Parent)
		} else {
			// use common parent
			mgr.AddProcessorWithParent(plg, parent)
		}
	}
	return mgr, nil
}
